Some notes on the evolution of software architectures – past, present and future outlook. Often the way one does architecture design can be a combination of philosophy and the use of patterns, practices and principals. Any software architecture is an abstraction that should contain at least one or more of the following:
- Structure
- Layers
- Components
- Relationships
Below are some examples of commonly found architectures.
Domain Centric
Traditional software architectures often used data centric focus where the database is at the center (or lowest layer) and everything is built on top of it. This put high emphasis on the data with strong inner dependencies towards it. Though this architecture is useful in some situations where high data focus is necessary, over time it was realized to put more emphasis on the goals of the application rather than the data. This created a shift towards domain centric architectures where the focus is on each domain, which can be application (or business) functions. This is a paradigm shift as now, instead of implementation focus (which is was data centric inherently did) the architectural view is more abstract. This provides more flexibility towards the implementation as it can be done in a variety of ways.
Some of the benefits of domain centric architecture is decoupling which allows a more agile approach to development. Development teams can focus on specific domains instead of complete systems. It also breaks the inner dependencies often found in data centric architectures. And as mentioned before, being that this is more of an abstract view it allows for more dynamic implementation processes being flexible to use different tools and technologies.
However, there are some cons to consider in domain centric architectures. Being a more abstract view, it requires more initial planning and design, which leads to a higher initial cost. Also, though there is some decoupling in domain centric architectures, some functions or parts of the system may still have tight inner dependencies to other sub-systems or components.
There are some different sub-types of domain centric architectures. For example, below is Alistair Cockburn’s hexagonal architecture where the layers branching out from the core domain are interfaced using ports or adapters. This approach focuses more on specific functions and which can be easily tested as there are no dependencies to specific implementations – such as UI or database.
Another example is the onion architecture by Jeffrey Palermo. Here the design is similar to the general domain centric approach but the key focus here is that the inner layers are distinct from the outer layers and has no view or dependencies towards them. Instead, it is the outer layers that interface with the inner layers and keep the focus going from out-to-in.
The last example is the clean architecture by Uncle Bob. Like the previous architectures it focuses on having outer layers interact with the inner layers using controllers, gateways and presenters. The inner layers have no dependencies towards the outer layers thereby allow flexibility to the implementation of databases, UI and other external interfaces.
Below shows that all the domain centric architectures keeps the domain focus in the middle core with dependencies go in and not out.
Deeper look into the Application Layer
When working with layers we have responsibilities split in vertical stack of different levels where each level has it’s own implementation and is isolated from the other layers and so can independently evolve. The application layer sits at the core and has interfaces to the other layers.
An example of how the application layer could be implemented is shown below. In this example, we are using dependency inversion principle where we use abstractions to have an inversion of control. This allows implementations to be focused on the abstraction layer which provides greater flexibility. In agile practices this conforms to the practice of development at the last responsible moment.
CQRS Architecture (Command Query Responsibility Separation)
Generally in object oriented programming we use commands to execute a change and queries to read data. There should be a clear distinction between commands and queries, each only doing their specific task. The CQRS architecture focuses in on this principle at the application layer. According to Martin Fowler, the separation between command and queries could be as far as having each run with it’s own domain models specific to that flow and even execute on separate processes or even separate hardware.
Having this separation provides benefits in keeping focus specific to that flow. In other words, commands can be optimized specific to execution whereas queries are specific to retrieval. This allows for support of different data stores. For example, the data write process could have a NoSQL based data store optimized for that execution whereas the query process could be from a normalized data store optimized for extraction. The data synchronization between the data stores can be done as part of the execution process/transaction or using some other eventual consistency pattern.
Screaming Architecture
Screaming architecture focuses on the use cases of the application. The architecture should clearly display those use cases and the intent for each of the components. For example, in a typical MVC application our code structure might be organized into each of those functional components – such as a folder for Controllers, Models and Views. However, in a screaming architecture project, our code structure might be organized by each of the use cases – such as a folder for Customers, Employees and Products.
The visual representation of screaming architecture is different than traditional types, often using a tree map to show each functional component of the application. This visualization also represents code structure, or folder structure, as well as showing which part have dependencies (by being grouped together by color, regions or location on the chart. Components next to each other represents higher cohesion). Examples of this are shown below.
The diagram above is actually layered. Below shows an example of how each region could be layered.
Microservices
Microservices further sub-divide system architecture into components and decouple these components to be self-contained. It divides the whole system into sub-systems that have interfaces to one another throguh clearly defined protocols. Microservices are a branch of SOA (Service Oriented Architecture). For example, in a scenario where we may have a customer purchases products and generates a sale, the product entity would be a domain model that is shared across different layers in the application. In a microservice architecture, this product domain model may be replicated in each of the components depending on how the subsystem is divided. There is no right answer here as the result could vary depending on the scenario.
Refer to my other blog for more details on microservices
http://solidfish.com/microservices/
From the microservices.io website, some pros and cons of using microservices are:
Pros | Cons |
Decoupling of application | Additional complexity |
Enables continuous delivery and deployment of large complex applications | Developer tools (IDEs) dont provide support for distributed applications |
Better testability (at least for specific services) | Difficult overall system testing (or integration tests over multiple services) |
Flexibility | Must support inter-communication |
Agile – decoupled; teams focused on specific services | Supporting use cases that go over multiple services causes difficulty in implementation and test |
Improved fault isolation | If each service is running in its own virtual machine or container, there could be significant overhead in running these instances |
Flexibility to technology stack – each service can run its own technology |
Testable Architecture
Testable architecture follows test-driven development. This follows the following steps:
- create failing tests that meet the requirements or use case
- get the test to pass by implementing the function
- improve the test and repeat
By following these steps we are driving the application design by tests. With the aid of tools we can setup different automation tests. Mike Cohn introduces a concept called Test Automation Pyramid where the tests towards the bottom of the pyramid are easier to automated and therefore inexpensive to create and maintain. In contrast, those tests towards the top are more complex and difficult to maintain, therefore more costly. The best practice is to focus on those tests at the bottom.
References
Onion Architecture Blog
https://dzone.com/articles/onion-architecture-is-interesting
Clean Architecture by Uncle Bob
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
Clean Architectures
https://app.pluralsight.com/library/courses/clean-architecture-patterns-practices-principles
Domain Centric Blog
https://blog.cancobanoglu.net/2017/05/11/domain-centric-architectures-are-cool-but-why/
Domain Centric Architectures are all the same
http://blog.ploeh.dk/2013/12/03/layers-onions-ports-adapters-its-all-the-same/
CQRS by Martin Fowler
https://martinfowler.com/bliki/CQRS.html
Microservices.io
http://microservices.io/patterns/microservices.html
Test Architecture
http://www.codingthearchitecture.com/2014/10/01/modularity_and_testability.html
Serverless Architectures
https://blog.symphonia.io/revisiting-serverless-architectures-29f0b831303c
Uses cases for Serverless Architectures
https://www.itopstimes.com/cloud/10-use-cases-for-serverless/